package cn.yangliu.init;

import cn.yangliu.init.annotations.InitJob;
import cn.yangliu.init.config.InitProperties;
import com.alibaba.excel.EasyExcel;
import lombok.Lombok;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;

import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

/**
 * The type of package-scan.
 *
 * <p>
 * .
 *
 * @author 问道于盲
 * @date 2020 -04-16
 */
@Slf4j
@Configuration
@EnableConfigurationProperties(InitProperties.class)
public class InitWorker implements ApplicationContextAware, InitializingBean {

    private static final Map<String, InitHandler> MAP = new HashMap<>();

    private static final InitWorker INSTANCE = new InitWorker();

    private ApplicationContext applicationContext;

    @Autowired
    private InitProperties properties;

    /**
     * 当所有的InitService都完成初始化的时候 才开始读取excel的数据
     */
    @Autowired
    private List<InitHandler> services;

    /**
     * current class's static member
     * The constant POOL.
     * 线程池, 只要一个线程来做初始化
     */
    public static final ExecutorService POOL = Executors.newSingleThreadExecutor();

    /**
     * 开始扫描
     */
    @SneakyThrows
    private void scan(InitProperties.Location location) {
        if (!properties.isEnable()) {
            return;
        }
        /**
         * 扫描包
         * 获取全部的excel数据模型类
         */
        List<Class<?>> classes = Scanner.scan(location.getScanPackage());

        classes = classes.stream().filter(cls-> cls.isAnnotationPresent(InitJob.class)).collect(Collectors.toList());

        /**
         * 创建初始化任务
         */
        List<InitAction> actions =
                classes.stream().map(this::buildAction).collect(Collectors.toList());

        /**
         * 排序
         * 初始化数据可能有先后顺序
         * 因此需要排序
         */
        actions.sort(Comparator.comparingInt(InitAction::order));

        Resource resource;

        /**
         * 判断初始化的excel是在磁盘上还是classpath下
         */
        if (location.getFilePath().startsWith("file:")) {
            resource = new FileSystemResource(location.getFilePath());
        }else {
            resource = new ClassPathResource(location.getFilePath());
        }

        /**
         * 开始初始化
         */
        actions.forEach(initAction -> doInit(initAction, resource));

        /**
         * 判断是否删除初始化文件
         */
        if (properties.isDeleteFileAfterSuccess() && (resource instanceof FileSystemResource)) {
            Files.delete(Paths.get(location.getFilePath()));
        }

    }

    /**
     * 开始初始化,执行初始化任务
     *
     * @param action 初始化任务
     * @param resource excel资源
     */
    private void doInit(InitAction<?> action, Resource resource) {
        try (InputStream is = resource.getInputStream()) {
            EasyExcel.read(is, action.modelClass(), action.listener()).sheet(action.sheet()).doRead();
        } catch (Exception e) {
            log.warn("init sheet " + action.sheet() + " faild", e.getMessage());
            /**
             * 初始化的异常处理逻辑
             * 配置了初始化异常不继续,则抛出异常
             */
            if (!properties.isContinueOnFail()) {
               throw Lombok.sneakyThrow(e);
            }
        }
    }

    /**
     * 创建初始化任务
     *
     * @param clazz 数据接收对象
     * @return 初始化任务
     */
    private InitAction<?> buildAction(Class<?> clazz) {
        InitJob job = clazz.getDeclaredAnnotation(InitJob.class);
        return new InitAction(
                job.sheet(),
                job.order(),
                clazz,
                job.listener());
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        INSTANCE.applicationContext = applicationContext;
    }

    /**
     * current class method for getter.
     * Gets application context.
     *
     * @return the application context
     */
    public static ApplicationContext getApplicationContext() {
        return INSTANCE.applicationContext;
    }


    @Override
    public void afterPropertiesSet() throws Exception {
        for (InitHandler service : services) {
            String className = service.getClass().getName();
            MAP.put(className, service);
        }
        InitProperties.Location[] locations = properties.getLocations();
        if (Objects.isNull(locations) || locations.length == 0) {
            return;
        }
        //异步
        if (properties.isAsync()) {
            InitProperties.Location[] finalLocations = locations;
            POOL.submit(() -> {
                for (InitProperties.Location location : finalLocations) {
                    scan(location);
                }
            });
            return;
        }

        //同步
        for (InitProperties.Location location : locations) {
            scan(location);
        }
        POOL.shutdown();
    }

}
